/* 
 * Copyright (C) 2007 Eskil Bylund
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

using System;
using System.Collections;
using System.Collections.Generic;

using DCSharp.Backend.Connections;

namespace DCSharp
{
	public class UserEventArgs : EventArgs
	{
		private User user;

		public UserEventArgs(User user)
		{
			this.user = user;
		}

		public User User
		{
			get { return user; }
		}
	}

	/// <summary>
	/// A class representing a unique user.
	/// </summary>
	/// <remarks>
	/// This class represents a single unique user, identified by a uid.
	/// 
	/// A user can be online on multiple hubs. The hubs can be enumerated on
	/// using EnumerateOnlineHubs.
	/// </remarks>
	public class User
	{
		private static Dictionary<Uid, WeakReference> users = new Dictionary<Uid,
			WeakReference>();

		public event EventHandler<ConnectionEventArgs> OnlineOnHub;
		public event EventHandler<ConnectionEventArgs> OfflineOnHub;
		public event EventHandler OnlineChanged;

		private List<HubConnection> hubs;
		private Uid uid;
		private string nick;
		private bool online;

		private User(Uid uid, string nick)
		{
			if (nick == null)
			{
				throw new ArgumentNullException("nick");
			}
			this.uid = uid;
			this.nick = nick;

			hubs = new List<HubConnection>();
		}

		#region Properties

		/// <summary>
		/// Gets the Uid of the user.
		/// </summary>
		/// <value>The Uid of the user.</value>
		public Uid Uid
		{
			get { return uid; }
			//set { uid = value; }
		}

		/// <summary>
		/// Gets the nickname of the user.
		/// </summary>
		/// <value>The nickname of the user.</value>
		public string Nick
		{
			get { return nick; }
			//set { nick = value; }
		}

		/// <summary>
		/// Gets whether or not the user is online.
		/// </summary>
		/// <value>Whether or not the user is online.</value>
		public bool IsOnline
		{
			get { return online; }
			private set
			{
				online = value;
				OnOnlineChanged();
			}
		}

		internal object SyncRoot
		{
			get { return hubs; }
		}

		#endregion

		#region Methods

		/// <summary>
		/// Creates or returns the user with the specified uid.
		/// </summary>
		/// <param name="uid">The uid of the user.</param>
		/// <param name="name">The nickname of the user.</param>
		/// <returns>The user with the specified uid.</returns>
		internal static User Create(Uid uid, string name)
		{
			lock (users)
			{
				User user = Get(uid);
				if (user == null)
				{
					user = new User(uid, name);
					users[uid] = new WeakReference(user);
				}
				return user;
			}
		}

		/// <summary>
		/// Returns the user with the specified uid.
		/// </summary>
		/// <param name="uid">The uid of the user.</param>
		/// <returns>The user with the specified uid.</returns>
		internal static User Get(Uid uid)
		{
			lock (users)
			{
				User user = null;
				WeakReference reference;
				if (users.TryGetValue(uid, out reference))
				{
					user = (User)reference.Target;
				}
				return user;
			}
		}

		/// <summary>
		/// Adds a specific hub to the list of online hubs.
		/// </summary>
		/// <param name="hub">The hub the user is online on.</param>
		internal void AddHub(HubConnection hub)
		{
			if (hub == null)
			{
				throw new ArgumentNullException("hub");
			}

			bool emitOnline = false;
			lock (hubs)
			{
				if (hubs.Contains(hub))
				{
					return;
				}
				hubs.Add(hub);
				emitOnline = hubs.Count == 1;
			}
			if (emitOnline)
			{
				IsOnline = true;
			}
			OnOnlineOnHub(hub);
		}

		/// <summary>
		/// Removes a specific hub from the list of online hubs.
		/// </summary>
		/// <param name="hub">The hub the user is no longer online on.</param>
		internal void RemoveHub(HubConnection hub)
		{
			if (hub == null)
			{
				throw new ArgumentNullException("hub");
			}

			bool emitOffline = false;
			lock (hubs)
			{
				if (!hubs.Contains(hub))
				{
					return;
				}
				hubs.Remove(hub);
				emitOffline = hubs.Count == 0;
			}
			if (emitOffline)
			{
				IsOnline = false;
			}
			OnOfflineOnHub(hub);
		}

		/// <summary>
		/// Checks if the user is online on a specific hub.
		/// </summary>
		/// <param name="hub">The hub to check.</param>
		/// <returns>True if the user is online on the hub; otherwise, false.</returns>
		public bool IsOnlineOnHub(HubConnection hub)
		{
			lock (hubs)
			{
				return hubs.Contains(hub);
			}
		}

		public void EnumerateOnlineHubs(Action<HubConnection> action)
		{
			lock (hubs)
			{
				foreach (HubConnection hub in hubs)
				{
					action(hub);
				}
			}
		}

		public override bool Equals(object a)
		{
			return this == a;
		}

		public override int GetHashCode()
		{
			return uid.GetHashCode() << 1;
		}

		public override string ToString()
		{
			return String.Format("{0} ({1})", nick, uid);
		}

		/// <summary>
		/// Emits the OnlineChanged event.
		/// </summary>
		protected void OnOnlineChanged()
		{
			if (OnlineChanged != null)
			{
				OnlineChanged(this, EventArgs.Empty);
			}
		}

		/// <summary>
		/// Emits the OnlineOnHub event.
		/// </summary>
		protected void OnOnlineOnHub(HubConnection hub)
		{
			if (OnlineOnHub != null)
			{
				OnlineOnHub(this, new ConnectionEventArgs(hub));
			}
		}

		/// <summary>
		/// Emits the OfflineOnHub event.
		/// </summary>
		protected void OnOfflineOnHub(HubConnection hub)
		{
			if (OfflineOnHub != null)
			{
				OfflineOnHub(this, new ConnectionEventArgs(hub));
			}
		}

		#endregion
	}
}
